﻿// -----------------------------
// Copyright © Kodi Studios 2012
// -----------------------------

using System;
using System.Diagnostics;
using System.Xml;
using System.IO;

namespace Kodi.Picturethrill
{
    /// <summary>
    /// Class for Windows Scheduled Tasks.
    /// </summary>
    public abstract class ScheduledTask
    {
        #region Private Properties

        private const string SchedulerProcessName = "schtasks";

        private string taskFileName;

        private string processName;

        // Example: \"Picturethrill.exe\"
        private string taskInputProcessName;

        // Example: "Picturethrill.exe"
        private string taskOutputProcessName;

        private string taskName;

        #endregion

        /// <summary>
        /// Scheduled task with a specific task name.
        /// </summary>
        /// <param name="taskName">Task name to use.</param>
        public ScheduledTask(string taskName, string taskFileName, string processName)
        {
            this.taskName = taskName;
            this.taskFileName = taskFileName;
            this.processName = processName;

            // Example: \"Picturethrill.exe\"
            this.taskInputProcessName = string.Format("\\\"{0}\\\"", this.processName);

            // Example: "Picturethrill.exe"
            this.taskOutputProcessName = string.Format("\"{0}\"", this.processName);
        }

        /// <summary>
        /// Creates Windows Task.
        /// </summary>
        public void CreateTask()
        {
            using (Log.Group())
            {
                // First delete Task, because if there is another task with the same name, 
                // it will not be able to create it. Error message from /Create:
                // ERROR: Cannot create a file when that file already exists.
                // Would be nice to check for exit code as well.
                this.DeleteTask();

                // Read Task XML
                XmlDocument taskDocument = new XmlDocument();
                taskDocument.Load(Path.Combine(Path.GetDirectoryName(this.processName), this.taskFileName));

                // Set process name
                XmlNamespaceManager nsmgr = new XmlNamespaceManager(taskDocument.NameTable);
                nsmgr.AddNamespace("ab", "http://schemas.microsoft.com/windows/2004/02/mit/task");
                //XmlElement xmlNode = (XmlElement)taskDocument.SelectSingleNode("ab:Task/ab:Actions/ab:Exec/ab:Command", nsmgr);
                XmlNode commandNode = taskDocument.SelectSingleNode("ab:Task/ab:Actions/ab:Exec/ab:Command", nsmgr);
                commandNode.InnerText = taskOutputProcessName;

                string picturethrillTaskPath = Path.Combine(Settings.TempPath, this.taskFileName);
                taskDocument.Save(picturethrillTaskPath);
                string picturethrillTaskPathQuoted = "\"" + picturethrillTaskPath + "\"";

                //schtasks /create /SC HOURLY /TN CalcTask2 /TR c:\Windows\System32\calc.exe
                string arguments = "/create /TN " + taskName + " /XML " + picturethrillTaskPathQuoted;

                // Load new settings
                using (Process p = new Process())
                {
                    p.StartInfo.FileName = SchedulerProcessName;
                    p.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
                    p.StartInfo.RedirectStandardOutput = true;
                    p.StartInfo.UseShellExecute = false;
                    p.StartInfo.Arguments = arguments;
                    p.StartInfo.CreateNoWindow = true;
                    p.Start();
                    p.WaitForExit();
                }
            }
        }

        /// <summary>
        /// Deletes task.
        /// </summary>
        public void DeleteTask()
        {
            using (Log.Group())
            {
                //schtasks /delete /F /TN CalcTask2
                using (Process p = new Process())
                {
                    p.StartInfo.FileName = SchedulerProcessName;
                    p.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
                    p.StartInfo.RedirectStandardOutput = true;
                    p.StartInfo.UseShellExecute = false;
                    p.StartInfo.Arguments = @"/delete /F /TN " + this.taskName;
                    p.StartInfo.CreateNoWindow = true;
                    p.Start();
                    p.WaitForExit();
                }
            }
        }

        /// <summary>
        /// Runs task.
        /// </summary>
        public void RunTask()
        {
            using (Log.Group())
            {
                //schtasks /run /TN CalcTask2
                using (Process p = new Process())
                {
                    p.StartInfo.FileName = SchedulerProcessName;
                    p.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
                    p.StartInfo.RedirectStandardOutput = true;
                    p.StartInfo.UseShellExecute = false;
                    p.StartInfo.Arguments = @"/run /TN " + this.taskName;
                    p.StartInfo.CreateNoWindow = true;
                    p.Start();
                    p.WaitForExit();
                }
            }
        }

        /// <summary>
        /// Checks whether given task exists.
        /// </summary>
        public bool TaskExists
        {
            get
            {
                string output = string.Empty;
                // Start the child process.
                using (Process schedulerProcess = new Process())
                {
                    string errorOutput = string.Empty;
                    // Redirect the output stream of the child process.
                    schedulerProcess.StartInfo.FileName = SchedulerProcessName;
                    schedulerProcess.StartInfo.Arguments = @"/Query /TN " + this.taskName + " /XML ONE";
                    schedulerProcess.StartInfo.UseShellExecute = false;
                    schedulerProcess.StartInfo.RedirectStandardOutput = true;
                    schedulerProcess.StartInfo.RedirectStandardError = true;
                    schedulerProcess.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
                    schedulerProcess.StartInfo.CreateNoWindow = true;

                    Log.Comment("Run: " + schedulerProcess.StartInfo.FileName + " " + schedulerProcess.StartInfo.Arguments);
                    schedulerProcess.Start();
                    // Do not wait for the child process to exit before
                    // reading to the end of its redirected stream.
                    // Read the output stream first and then wait.
                    output = schedulerProcess.StandardOutput.ReadToEnd();
                    errorOutput = schedulerProcess.StandardError.ReadToEnd();
                    schedulerProcess.WaitForExit();

                    Log.Comment("Exit Code: " + schedulerProcess.ExitCode);
                    Log.Comment("Error Output: " + errorOutput);
                    if (schedulerProcess.ExitCode == 1)
                    {
                        // error output is 
                        // ERROR: The system cannot find the file specified.
                        return false;
                    }
                }

                XmlDocument xmlDocument = new XmlDocument();
                try
                {
                    xmlDocument.LoadXml(output);
                    XmlNamespaceManager nsmgr = new XmlNamespaceManager(xmlDocument.NameTable);
                    nsmgr.AddNamespace("ab", "http://schemas.microsoft.com/windows/2004/02/mit/task");
                    XmlElement xmlNode = (XmlElement)xmlDocument.SelectSingleNode("ab:Task/ab:Actions/ab:Exec/ab:Command", nsmgr);

                    if (string.Equals(xmlNode.InnerText, this.taskOutputProcessName, StringComparison.OrdinalIgnoreCase))
                    {
                        return true;
                    }
                }
                catch (XmlException)
                {
                    // error output is 
                    // ERROR: The system cannot find the file specified.
                }
                return false;
            }
        }
    }
}
